{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "colab": {
      "name": "intro_to_sparse_data_and_embeddings.ipynb",
      "version": "0.3.2",
      "views": {},
      "default_view": {},
      "provenance": [],
      "collapsed_sections": [
        "mNCLhxsXyOIS",
        "eQS5KQzBybTY",
        "copyright-notice"
      ]
    },
    "kernelspec": {
      "name": "python3",
      "display_name": "Python 3"
    }
  },
  "cells": [
    {
      "source": [
        "#### Copyright 2017 Google LLC."
      ],
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "copyright-notice"
      }
    },
    {
      "outputs": [],
      "source": [
        "# Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "# https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ],
      "cell_type": "code",
      "metadata": {
        "cellView": "both",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        },
        "colab_type": "code",
        "id": "copyright-notice2"
      },
      "execution_count": 0
    },
    {
      "metadata": {
        "id": "PTaAdgy3LS8W",
        "colab_type": "text"
      },
      "cell_type": "markdown",
      "source": [
        " # Introducci\u00f3n a los datos dispersos y las incorporaciones\n",
        "\n",
        "**Objetivos de aprendizaje:**\n",
        "* convertir datos de strings de rese\u00f1as de pel\u00edculas en un vector de atributos dispersos\n",
        "* implementar un modelo lineal de an\u00e1lisis de opiniones a trav\u00e9s de un vector de atributos dispersos\n",
        "* implementar un modelo de RNP de an\u00e1lisis de opiniones a trav\u00e9s de una incorporaci\u00f3n que proyecte datos en dos dimensiones\n",
        "* visualizar la incorporaci\u00f3n para observar qu\u00e9 aprendi\u00f3 el modelo acerca de las relaciones entre las palabras\n",
        "\n",
        "En este ejercicio, exploraremos datos dispersos y trabajaremos con incorporaciones mediante el uso de datos de texto de rese\u00f1as de pel\u00edculas (del [conjunto de datos de IMDB de ACL 2011](http://ai.stanford.edu/~amaas/data/sentiment/). Estos datos ya se procesaron en formato `tf.Example`.  "
      ]
    },
    {
      "metadata": {
        "id": "2AKGtmwNosU8",
        "colab_type": "text"
      },
      "cell_type": "markdown",
      "source": [
        " ## Preparaci\u00f3n\n",
        "\n",
        "Importemos nuestras dependencias y descarguemos los datos de entrenamiento y de prueba. [`tf.keras`](https://www.tensorflow.org/api_docs/python/tf/keras) incluye una herramienta de almacenamiento en cach\u00e9 y descarga de archivos que podemos usar para recuperar los conjuntos de datos."
      ]
    },
    {
      "metadata": {
        "id": "jGWqDqFFL_NZ",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        }
      },
      "source": [
        "from __future__ import print_function\n",
        "\n",
        "import collections\n",
        "import io\n",
        "import math\n",
        "\n",
        "import matplotlib.pyplot as plt\n",
        "import numpy as np\n",
        "import pandas as pd\n",
        "import tensorflow as tf\n",
        "from IPython import display\n",
        "from sklearn import metrics\n",
        "\n",
        "tf.logging.set_verbosity(tf.logging.ERROR)\n",
        "train_url = 'https://download.mlcc.google.com/mledu-datasets/sparse-data-embedding/train.tfrecord'\n",
        "train_path = tf.keras.utils.get_file(train_url.split('/')[-1], train_url)\n",
        "test_url = 'https://download.mlcc.google.com/mledu-datasets/sparse-data-embedding/test.tfrecord'\n",
        "test_path = tf.keras.utils.get_file(test_url.split('/')[-1], test_url)"
      ],
      "cell_type": "code",
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "6W7aZ9qspZVj",
        "colab_type": "text"
      },
      "cell_type": "markdown",
      "source": [
        " ## Desarrollo de un modelo de an\u00e1lisis de opiniones"
      ]
    },
    {
      "metadata": {
        "id": "jieA0k_NLS8a",
        "colab_type": "text"
      },
      "cell_type": "markdown",
      "source": [
        " Entrenemos un modelo de an\u00e1lisis de opiniones con estos datos, que prediga si una rese\u00f1a es en general *favorable* (etiqueta de 1) o *desfavorable* (etiqueta de 0).\n",
        "\n",
        "Para eso, convertiremos nuestros `terms` con valores de strings en vectores de atributos a trav\u00e9s de un *vocabulario*, una lista de cada t\u00e9rmino que esperamos ver en nuestros datos. Para los fines de este ejercicio, creamos un peque\u00f1o vocabulario que se enfoca en un conjunto de t\u00e9rminos limitado. La mayor\u00eda de estos se consideraron fuertes indicadores de *favorable* o *unfavorable*, pero algunos se agregaron simplemente porque son interesantes.\n",
        "\n",
        "Cada t\u00e9rmino del vocabulario est\u00e1 asignado a una coordenada de nuestro vector de atributos. Para convertir los `terms` con valores de strings para un ejemplo en este formato de vector, los codificamos de manera tal que cada coordenada obtenga un valor de 0 si el t\u00e9rmino del vocabulario no aparece en la string de ejemplo y un valor de 1 si aparece. Los t\u00e9rminos de un ejemplo que no aparecen en el vocabulario se descartan."
      ]
    },
    {
      "metadata": {
        "id": "2HSfklfnLS8b",
        "colab_type": "text"
      },
      "cell_type": "markdown",
      "source": [
        " **NOTA:** *Desde luego, podr\u00edamos usar un vocabulario m\u00e1s grande. Tambi\u00e9n existen herramientas especiales para crearlos. Adem\u00e1s, en lugar de simplemente descartar los t\u00e9rminos que no est\u00e1n en el vocabulario, podemos incorporar una peque\u00f1a cantidad de agrupamientos OOV (fuera de vocabulario), con los cuales se puede generar un hash para los t\u00e9rminos que no est\u00e1n en el vocabulario. A su vez, podemos usar un enfoque de __generaci\u00f3n de hashes de atributos__ que genere hashes de cada t\u00e9rmino, en lugar de crear un vocabulario expl\u00edcito. Esto funciona bien en la pr\u00e1ctica, pero dificulta la interpretabilidad, que es \u00fatil para este ejercicio. Consulta el m\u00f3dulo tf.feature_column para obtener herramientas para abordar esto.*"
      ]
    },
    {
      "metadata": {
        "id": "Uvoa2HyDtgqe",
        "colab_type": "text"
      },
      "cell_type": "markdown",
      "source": [
        " ## Desarrollo de la canalizaci\u00f3n de entrada"
      ]
    },
    {
      "metadata": {
        "id": "O20vMEOurDol",
        "colab_type": "text"
      },
      "cell_type": "markdown",
      "source": [
        " Primero, configuremos la canalizaci\u00f3n de entrada para importar nuestros datos a un modelo de TensorFlow. Podemos usar la siguiente funci\u00f3n para analizar los datos de entrenamiento y de prueba (que se encuentran en formato [TFRecord](https://www.tensorflow.org/guide/datasets#consuming_tfrecord_data)) y devolver un diccionario de los atributos y etiquetas correspondientes."
      ]
    },
    {
      "metadata": {
        "id": "SxxNIEniPq2z",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        }
      },
      "source": [
        "def _parse_function(record):\n",
        "  \"\"\"Extracts features and labels.\n",
        "  \n",
        "  Args:\n",
        "    record: File path to a TFRecord file    \n",
        "  Returns:\n",
        "    A `tuple` `(labels, features)`:\n",
        "      features: A dict of tensors representing the features\n",
        "      labels: A tensor with the corresponding labels.\n",
        "  \"\"\"\n",
        "  features = {\n",
        "    \"terms\": tf.VarLenFeature(dtype=tf.string), # terms are strings of varying lengths\n",
        "    \"labels\": tf.FixedLenFeature(shape=[1], dtype=tf.float32) # labels are 0 or 1\n",
        "  }\n",
        "  \n",
        "  parsed_features = tf.parse_single_example(record, features)\n",
        "  \n",
        "  terms = parsed_features['terms'].values\n",
        "  labels = parsed_features['labels']\n",
        "\n",
        "  return  {'terms':terms}, labels"
      ],
      "cell_type": "code",
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "SXhTeeYMrp-l",
        "colab_type": "text"
      },
      "cell_type": "markdown",
      "source": [
        " Para confirmar que nuestra funci\u00f3n se desempe\u00f1a de acuerdo con lo esperado, construyamos un `TFRecordDataset` para los datos de entrenamiento y asignemos los datos a atributos y etiquetas a trav\u00e9s del atributo que se incluy\u00f3 m\u00e1s arriba."
      ]
    },
    {
      "metadata": {
        "id": "oF4YWXR0Omt0",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        }
      },
      "source": [
        "# Create the Dataset object.\n",
        "ds = tf.data.TFRecordDataset(train_path)\n",
        "# Map features and labels with the parse function.\n",
        "ds = ds.map(_parse_function)\n",
        "\n",
        "ds"
      ],
      "cell_type": "code",
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "bUoMvK-9tVXP",
        "colab_type": "text"
      },
      "cell_type": "markdown",
      "source": [
        " Ejecuta la siguiente celda para recuperar el primer ejemplo del conjunto de datos de entrenamiento."
      ]
    },
    {
      "metadata": {
        "id": "Z6QE2DWRUc4E",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        }
      },
      "source": [
        "n = ds.make_one_shot_iterator().get_next()\n",
        "sess = tf.Session()\n",
        "sess.run(n)"
      ],
      "cell_type": "code",
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "jBU39UeFty9S",
        "colab_type": "text"
      },
      "cell_type": "markdown",
      "source": [
        " Ahora, desarrollemos una funci\u00f3n de entrada formal que podamos pasar al m\u00e9todo `train()` de un objeto de Estimator de TensorFlow."
      ]
    },
    {
      "metadata": {
        "id": "5_C5-ueNYIn_",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        }
      },
      "source": [
        "# Create an input_fn that parses the tf.Examples from the given files,\n",
        "# and split them into features and targets.\n",
        "def _input_fn(input_filenames, num_epochs=None, shuffle=True):\n",
        "  \n",
        "  # Same code as above; create a dataset and map features and labels.\n",
        "  ds = tf.data.TFRecordDataset(input_filenames)\n",
        "  ds = ds.map(_parse_function)\n",
        "\n",
        "  if shuffle:\n",
        "    ds = ds.shuffle(10000)\n",
        "\n",
        "  # Our feature data is variable-length, so we pad and batch\n",
        "  # each field of the dataset structure to whatever size is necessary.     \n",
        "  ds = ds.padded_batch(25, ds.output_shapes)\n",
        "  \n",
        "  ds = ds.repeat(num_epochs)\n",
        "\n",
        "  \n",
        "  # Return the next batch of data.\n",
        "  features, labels = ds.make_one_shot_iterator().get_next()\n",
        "  return features, labels"
      ],
      "cell_type": "code",
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "Y170tVlrLS8c",
        "colab_type": "text"
      },
      "cell_type": "markdown",
      "source": [
        " ## Tarea\u00a01: Usar un modelo lineal con entradas dispersas y vocabulario expl\u00edcito\n",
        "\n",
        "Para nuestro primer modelo, desarrollaremos un modelo de [`LinearClassifier`](https://www.tensorflow.org/api_docs/python/tf/estimator/LinearClassifier) con 50 t\u00e9rminos informativos; siempre debemos comenzar por lo m\u00e1s simple.\n",
        "\n",
        "El siguiente c\u00f3digo genera la columna de atributos para nuestros t\u00e9rminos. El atributo [`categorical_column_with_vocabulary_list`](https://www.tensorflow.org/api_docs/python/tf/feature_column/categorical_column_with_vocabulary_list) crea una columna de funciones con la asignaci\u00f3n de string a vector de funciones."
      ]
    },
    {
      "metadata": {
        "id": "B5gdxuWsvPcx",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        }
      },
      "source": [
        "# 50 informative terms that compose our model vocabulary. \n",
        "informative_terms = (\"bad\", \"great\", \"best\", \"worst\", \"fun\", \"beautiful\",\n",
        "                     \"excellent\", \"poor\", \"boring\", \"awful\", \"terrible\",\n",
        "                     \"definitely\", \"perfect\", \"liked\", \"worse\", \"waste\",\n",
        "                     \"entertaining\", \"loved\", \"unfortunately\", \"amazing\",\n",
        "                     \"enjoyed\", \"favorite\", \"horrible\", \"brilliant\", \"highly\",\n",
        "                     \"simple\", \"annoying\", \"today\", \"hilarious\", \"enjoyable\",\n",
        "                     \"dull\", \"fantastic\", \"poorly\", \"fails\", \"disappointing\",\n",
        "                     \"disappointment\", \"not\", \"him\", \"her\", \"good\", \"time\",\n",
        "                     \"?\", \".\", \"!\", \"movie\", \"film\", \"action\", \"comedy\",\n",
        "                     \"drama\", \"family\")\n",
        "\n",
        "terms_feature_column = tf.feature_column.categorical_column_with_vocabulary_list(key=\"terms\", vocabulary_list=informative_terms)"
      ],
      "cell_type": "code",
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "eTiDwyorwd3P",
        "colab_type": "text"
      },
      "cell_type": "markdown",
      "source": [
        " A continuaci\u00f3n, generaremos el `LinearClassifier`, lo entrenaremos con el conjunto de entrenamiento y lo evaluaremos con el conjunto de evaluaci\u00f3n. Despu\u00e9s de leer todo el c\u00f3digo, ejec\u00fatalo y observa el desempe\u00f1o."
      ]
    },
    {
      "metadata": {
        "id": "HYKKpGLqLS8d",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        }
      },
      "source": [
        "my_optimizer = tf.train.AdagradOptimizer(learning_rate=0.1)\n",
        "my_optimizer = tf.contrib.estimator.clip_gradients_by_norm(my_optimizer, 5.0)\n",
        "\n",
        "feature_columns = [ terms_feature_column ]\n",
        "\n",
        "\n",
        "classifier = tf.estimator.LinearClassifier(\n",
        "  feature_columns=feature_columns,\n",
        "  optimizer=my_optimizer,\n",
        ")\n",
        "\n",
        "classifier.train(\n",
        "  input_fn=lambda: _input_fn([train_path]),\n",
        "  steps=1000)\n",
        "\n",
        "evaluation_metrics = classifier.evaluate(\n",
        "  input_fn=lambda: _input_fn([train_path]),\n",
        "  steps=1000)\n",
        "print(\"Training set metrics:\")\n",
        "for m in evaluation_metrics:\n",
        "  print(m, evaluation_metrics[m])\n",
        "print(\"---\")\n",
        "\n",
        "evaluation_metrics = classifier.evaluate(\n",
        "  input_fn=lambda: _input_fn([test_path]),\n",
        "  steps=1000)\n",
        "\n",
        "print(\"Test set metrics:\")\n",
        "for m in evaluation_metrics:\n",
        "  print(m, evaluation_metrics[m])\n",
        "print(\"---\")"
      ],
      "cell_type": "code",
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "J0ubn9gULS8g",
        "colab_type": "text"
      },
      "cell_type": "markdown",
      "source": [
        " ## Tarea\u00a02: Usar un modelo de redes neuronales profundas (RNP)\n",
        "\n",
        "El modelo anterior es un modelo lineal que funciona bastante bien. Pero, \u00bfpodemos obtener un mejor desempe\u00f1o con un modelo de RNP?\n",
        "\n",
        "Cambiemos a un [`DNNClassifier`](https://www.tensorflow.org/api_docs/python/tf/estimator/DNNClassifier) en lugar del `LinearClassifier`. Ejecuta la siguiente celda y observa qu\u00e9 desempe\u00f1o obtienes."
      ]
    },
    {
      "metadata": {
        "id": "jcgOPfEALS8h",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        }
      },
      "source": [
        "##################### Here's what we changed ##################################\n",
        "classifier = tf.estimator.DNNClassifier(                                      #\n",
        "  feature_columns=[tf.feature_column.indicator_column(terms_feature_column)], #\n",
        "  hidden_units=[20,20],                                                       #\n",
        "  optimizer=my_optimizer,                                                     #\n",
        ")                                                                             #\n",
        "###############################################################################\n",
        "\n",
        "try:\n",
        "  classifier.train(\n",
        "    input_fn=lambda: _input_fn([train_path]),\n",
        "    steps=1000)\n",
        "\n",
        "  evaluation_metrics = classifier.evaluate(\n",
        "    input_fn=lambda: _input_fn([train_path]),\n",
        "    steps=1)\n",
        "  print(\"Training set metrics:\")\n",
        "  for m in evaluation_metrics:\n",
        "    print(m, evaluation_metrics[m])\n",
        "  print(\"---\")\n",
        "\n",
        "  evaluation_metrics = classifier.evaluate(\n",
        "    input_fn=lambda: _input_fn([test_path]),\n",
        "    steps=1)\n",
        "\n",
        "  print(\"Test set metrics:\")\n",
        "  for m in evaluation_metrics:\n",
        "    print(m, evaluation_metrics[m])\n",
        "  print(\"---\")\n",
        "except ValueError as err:\n",
        "  print(err)"
      ],
      "cell_type": "code",
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "cZz68luxLS8j",
        "colab_type": "text"
      },
      "cell_type": "markdown",
      "source": [
        " ## Tarea\u00a03: Usar una incorporaci\u00f3n con un modelo de RNP\n",
        "\n",
        "En esta tarea, implementaremos nuestro modelo de RNP mediante el uso de una columna de incorporaciones. Una columna de incorporaciones toma datos dispersos como entrada y devuelve un vector denso de dimensiones m\u00e1s bajas como resultado."
      ]
    },
    {
      "metadata": {
        "id": "AliRzhvJLS8k",
        "colab_type": "text"
      },
      "cell_type": "markdown",
      "source": [
        " **NOTA:** *Una embedding_column suele ser la opci\u00f3n m\u00e1s eficaz con relaci\u00f3n al c\u00f3mputo que se puede usar para entrenar un modelo con datos dispersos. En una [secci\u00f3n opcional](#scrollTo=XDMlGgRfKSVz) al final de este ejercicio, analizaremos en m\u00e1s detalle las diferencias de implementaci\u00f3n entre el uso de una `embedding_column` y una `indicator_column`, as\u00ed como las ventajas y desventajas de seleccionar una o la otra.*"
      ]
    },
    {
      "metadata": {
        "id": "F-as3PtALS8l",
        "colab_type": "text"
      },
      "cell_type": "markdown",
      "source": [
        " En el siguiente c\u00f3digo, realiza lo siguiente:\n",
        "\n",
        "* Define las columnas de atributos para el modelo a trav\u00e9s de una `embedding_column` que proyecte los datos en 2 dimensiones (para obtener m\u00e1s detalles sobre la firma de funciones para `embedding_column`, consulta la [documentaci\u00f3n de TF](https://www.tensorflow.org/api_docs/python/tf/feature_column/embedding_column)).\n",
        "* Define un `DNNClassifier` con las siguientes especificaciones:\n",
        "  * Dos capas ocultas de 20 unidades cada una\n",
        "  * Optimizaci\u00f3n de AdaGrad con una tasa de aprendizaje de 0.1\n",
        "  * Una `gradient_clip_norm` de 5.0"
      ]
    },
    {
      "metadata": {
        "id": "UlPZ-Q9bLS8m",
        "colab_type": "text"
      },
      "cell_type": "markdown",
      "source": [
        " **NOTA:** *En la pr\u00e1ctica, es posible que proyectemos en m\u00e1s que 2 dimensiones, como 50 o 100. Pero, por ahora, 2 dimensiones son f\u00e1ciles de visualizar.*"
      ]
    },
    {
      "metadata": {
        "id": "mNCLhxsXyOIS",
        "colab_type": "text"
      },
      "cell_type": "markdown",
      "source": [
        " ### Sugerencia"
      ]
    },
    {
      "metadata": {
        "id": "L67xYD7hLS8m",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        }
      },
      "source": [
        "# Here's a example code snippet you might use to define the feature columns:\n",
        "\n",
        "terms_embedding_column = tf.feature_column.embedding_column(terms_feature_column, dimension=2)\n",
        "feature_columns = [ terms_embedding_column ]"
      ],
      "cell_type": "code",
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "iv1UBsJxyV37",
        "colab_type": "text"
      },
      "cell_type": "markdown",
      "source": [
        " ### Completa el c\u00f3digo a continuaci\u00f3n"
      ]
    },
    {
      "metadata": {
        "id": "5PG_yhNGLS8u",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        }
      },
      "source": [
        "########################## YOUR CODE HERE ######################################\n",
        "terms_embedding_column = # Define the embedding column\n",
        "feature_columns = # Define the feature columns\n",
        "\n",
        "classifier = # Define the DNNClassifier\n",
        "################################################################################\n",
        "\n",
        "classifier.train(\n",
        "  input_fn=lambda: _input_fn([train_path]),\n",
        "  steps=1000)\n",
        "\n",
        "evaluation_metrics = classifier.evaluate(\n",
        "  input_fn=lambda: _input_fn([train_path]),\n",
        "  steps=1000)\n",
        "print(\"Training set metrics:\")\n",
        "for m in evaluation_metrics:\n",
        "  print(m, evaluation_metrics[m])\n",
        "print(\"---\")\n",
        "\n",
        "evaluation_metrics = classifier.evaluate(\n",
        "  input_fn=lambda: _input_fn([test_path]),\n",
        "  steps=1000)\n",
        "\n",
        "print(\"Test set metrics:\")\n",
        "for m in evaluation_metrics:\n",
        "  print(m, evaluation_metrics[m])\n",
        "print(\"---\")"
      ],
      "cell_type": "code",
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "eQS5KQzBybTY",
        "colab_type": "text"
      },
      "cell_type": "markdown",
      "source": [
        " ### Soluci\u00f3n\n",
        "\n",
        "Haz clic m\u00e1s abajo para conocer la soluci\u00f3n."
      ]
    },
    {
      "metadata": {
        "id": "R5xOdYeQydi5",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        }
      },
      "source": [
        "########################## SOLUTION CODE ########################################\n",
        "terms_embedding_column = tf.feature_column.embedding_column(terms_feature_column, dimension=2)\n",
        "feature_columns = [ terms_embedding_column ]\n",
        "\n",
        "my_optimizer = tf.train.AdagradOptimizer(learning_rate=0.1)\n",
        "my_optimizer = tf.contrib.estimator.clip_gradients_by_norm(my_optimizer, 5.0)\n",
        "\n",
        "classifier = tf.estimator.DNNClassifier(\n",
        "  feature_columns=feature_columns,\n",
        "  hidden_units=[20,20],\n",
        "  optimizer=my_optimizer\n",
        ")\n",
        "#################################################################################\n",
        "\n",
        "classifier.train(\n",
        "  input_fn=lambda: _input_fn([train_path]),\n",
        "  steps=1000)\n",
        "\n",
        "evaluation_metrics = classifier.evaluate(\n",
        "  input_fn=lambda: _input_fn([train_path]),\n",
        "  steps=1000)\n",
        "print(\"Training set metrics:\")\n",
        "for m in evaluation_metrics:\n",
        "  print(m, evaluation_metrics[m])\n",
        "print(\"---\")\n",
        "\n",
        "evaluation_metrics = classifier.evaluate(\n",
        "  input_fn=lambda: _input_fn([test_path]),\n",
        "  steps=1000)\n",
        "\n",
        "print(\"Test set metrics:\")\n",
        "for m in evaluation_metrics:\n",
        "  print(m, evaluation_metrics[m])\n",
        "print(\"---\")"
      ],
      "cell_type": "code",
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "aiHnnVtzLS8w",
        "colab_type": "text"
      },
      "cell_type": "markdown",
      "source": [
        " ## Tarea\u00a04: Convencerte de que realmente hay una incorporaci\u00f3n all\u00ed\n",
        "\n",
        "El modelo anterior utiliz\u00f3 una `embedding_column` y pareci\u00f3 tener un buen desempe\u00f1o, pero esto no nos indica mucho qu\u00e9 ocurre internamente. \u00bfC\u00f3mo podemos comprobar que el modelo realmente est\u00e1 usando una incorporaci\u00f3n dentro?\n",
        "\n",
        "Para comenzar, observemos los tensores del modelo:"
      ]
    },
    {
      "metadata": {
        "id": "h1jNgLdQLS8w",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        }
      },
      "source": [
        "classifier.get_variable_names()"
      ],
      "cell_type": "code",
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "Sl4-VctMLS8z",
        "colab_type": "text"
      },
      "cell_type": "markdown",
      "source": [
        " Bien, podemos observar que hay una capa de incorporaciones all\u00ed: `'dnn/input_from_feature_columns/input_layer/terms_embedding/...'`. (De hecho, lo interesante aqu\u00ed es que esta capa se puede entrenar junto con el resto del modelo, al igual que cualquier capa oculta).\n",
        "\n",
        "\u00bfLa capa de incorporaciones tiene la forma correcta? Ejecuta el siguiente c\u00f3digo para descubrirlo."
      ]
    },
    {
      "metadata": {
        "id": "JNFxyQUiLS80",
        "colab_type": "text"
      },
      "cell_type": "markdown",
      "source": [
        " **NOTA:** *Recuerda que, en nuestro caso, la incorporaci\u00f3n es una matriz que nos permite proyectar un vector de 50 dimensiones en 2 dimensiones.*"
      ]
    },
    {
      "metadata": {
        "id": "1xMbpcEjLS80",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        }
      },
      "source": [
        "classifier.get_variable_value('dnn/input_from_feature_columns/input_layer/terms_embedding/embedding_weights').shape"
      ],
      "cell_type": "code",
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "MnLCIogjLS82",
        "colab_type": "text"
      },
      "cell_type": "markdown",
      "source": [
        " Dedica un tiempo a comprobar manualmente las diferentes capas y formas a fin de asegurarte de que todo est\u00e9 conectado seg\u00fan lo esperado."
      ]
    },
    {
      "metadata": {
        "id": "rkKAaRWDLS83",
        "colab_type": "text"
      },
      "cell_type": "markdown",
      "source": [
        " ## Tarea\u00a05: Examinar la incorporaci\u00f3n\n",
        "\n",
        "Ahora observemos el espacio de incorporaci\u00f3n real y veamos d\u00f3nde acaban los t\u00e9rminos dentro de este. Realiza lo siguiente:\n",
        "1. Ejecuta el c\u00f3digo que aparece a continuaci\u00f3n para ver la incorporaci\u00f3n que entrenamos en la **Tarea\u00a03**. \u00bfLos t\u00e9rminos est\u00e1n donde esperabas?\n",
        "\n",
        "2. Ejecutar el c\u00f3digo de la **Tarea\u00a03** otra vez para volver a entrenar el modelo y, luego, vuelve a ejecutar la visualizaci\u00f3n de la incorporaci\u00f3n que aparece m\u00e1s abajo. \u00bfQu\u00e9 permanece igual? \u00bfQu\u00e9 cambia?\n",
        "\n",
        "3. Finalmente, vuelve a entrenar el modelo con solo 10 pasos (lo cual producir\u00e1 un modelo terrible). Vuelve a ejecutar la visualizaci\u00f3n de la incorporaci\u00f3n que aparece m\u00e1s abajo. \u00bfQu\u00e9 ves ahora y por qu\u00e9?"
      ]
    },
    {
      "metadata": {
        "id": "s4NNu7KqLS84",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        }
      },
      "source": [
        "import numpy as np\n",
        "import matplotlib.pyplot as plt\n",
        "\n",
        "embedding_matrix = classifier.get_variable_value('dnn/input_from_feature_columns/input_layer/terms_embedding/embedding_weights')\n",
        "\n",
        "for term_index in range(len(informative_terms)):\n",
        "  # Create a one-hot encoding for our term.  It has 0s everywhere, except for\n",
        "  # a single 1 in the coordinate that corresponds to that term.\n",
        "  term_vector = np.zeros(len(informative_terms))\n",
        "  term_vector[term_index] = 1\n",
        "  # We'll now project that one-hot vector into the embedding space.\n",
        "  embedding_xy = np.matmul(term_vector, embedding_matrix)\n",
        "  plt.text(embedding_xy[0],\n",
        "           embedding_xy[1],\n",
        "           informative_terms[term_index])\n",
        "\n",
        "# Do a little setup to make sure the plot displays nicely.\n",
        "plt.rcParams[\"figure.figsize\"] = (15, 15)\n",
        "plt.xlim(1.2 * embedding_matrix.min(), 1.2 * embedding_matrix.max())\n",
        "plt.ylim(1.2 * embedding_matrix.min(), 1.2 * embedding_matrix.max())\n",
        "plt.show() "
      ],
      "cell_type": "code",
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "pUb3L7pqLS86",
        "colab_type": "text"
      },
      "cell_type": "markdown",
      "source": [
        " ## Tarea\u00a06: Intentar mejorar el rendimiento del modelo\n",
        "\n",
        "Ve si puedes ajustar el modelo para mejorar el rendimiento. A continuaci\u00f3n, se indican algunas acciones que puedes probar:\n",
        "\n",
        "* **Cambiar los hiperpar\u00e1metros** o **usar un optimizador diferente**, como Adam (es posible que solo ganes uno o dos puntos en el porcentaje de exactitud con estas estrategias).\n",
        "* **Agregar t\u00e9rminos adicionales a `informative_terms`.** Hay un archivo de vocabulario completo con los 30,716 t\u00e9rminos para este conjunto de datos que puedes usar en https://download.mlcc.google.com/mledu-datasets/sparse-data-embedding/terms.txt. Puedes seleccionar t\u00e9rminos adicionales de este archivo de vocabulario o usar el archivo completo a trav\u00e9s de la columna de atributos `categorical_column_with_vocabulary_file`."
      ]
    },
    {
      "metadata": {
        "id": "6-b3BqXvLS86",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        }
      },
      "source": [
        "# Download the vocabulary file.\n",
        "terms_url = 'https://download.mlcc.google.com/mledu-datasets/sparse-data-embedding/terms.txt'\n",
        "terms_path = tf.keras.utils.get_file(terms_url.split('/')[-1], terms_url)"
      ],
      "cell_type": "code",
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "0jbJlwW5LS8-",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        }
      },
      "source": [
        "# Create a feature column from \"terms\", using a full vocabulary file.\n",
        "informative_terms = None\n",
        "with io.open(terms_path, 'r', encoding='utf8') as f:\n",
        "  # Convert it to a set first to remove duplicates.\n",
        "  informative_terms = list(set(f.read().split()))\n",
        "  \n",
        "terms_feature_column = tf.feature_column.categorical_column_with_vocabulary_list(key=\"terms\", \n",
        "                                                                                 vocabulary_list=informative_terms)\n",
        "\n",
        "terms_embedding_column = tf.feature_column.embedding_column(terms_feature_column, dimension=2)\n",
        "feature_columns = [ terms_embedding_column ]\n",
        "\n",
        "my_optimizer = tf.train.AdagradOptimizer(learning_rate=0.1)\n",
        "my_optimizer = tf.contrib.estimator.clip_gradients_by_norm(my_optimizer, 5.0)\n",
        "\n",
        "classifier = tf.estimator.DNNClassifier(\n",
        "  feature_columns=feature_columns,\n",
        "  hidden_units=[10,10],\n",
        "  optimizer=my_optimizer\n",
        ")\n",
        "\n",
        "classifier.train(\n",
        "  input_fn=lambda: _input_fn([train_path]),\n",
        "  steps=1000)\n",
        "\n",
        "evaluation_metrics = classifier.evaluate(\n",
        "  input_fn=lambda: _input_fn([train_path]),\n",
        "  steps=1000)\n",
        "print(\"Training set metrics:\")\n",
        "for m in evaluation_metrics:\n",
        "  print(m, evaluation_metrics[m])\n",
        "print(\"---\")\n",
        "\n",
        "evaluation_metrics = classifier.evaluate(\n",
        "  input_fn=lambda: _input_fn([test_path]),\n",
        "  steps=1000)\n",
        "\n",
        "print(\"Test set metrics:\")\n",
        "for m in evaluation_metrics:\n",
        "  print(m, evaluation_metrics[m])\n",
        "print(\"---\")"
      ],
      "cell_type": "code",
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "ew3kwGM-LS9B",
        "colab_type": "text"
      },
      "cell_type": "markdown",
      "source": [
        " ## Conclusi\u00f3n\n",
        "\n",
        "Es posible que hayamos obtenido una soluci\u00f3n de RNP con una incorporaci\u00f3n que se desempe\u00f1\u00f3 mejor que nuestro modelo lineal original, pero el modelo lineal tambi\u00e9n era bastante bueno y algo m\u00e1s r\u00e1pido para entrenar. Los modelos lineales se entrenan m\u00e1s r\u00e1pidamente porque no tienen tantos par\u00e1metros para actualizar o capas para realizar propagaci\u00f3n inversa.\n",
        "\n",
        "En algunas aplicaciones, la velocidad de los modelos lineales puede cambiar las reglas del juego. A veces, los modelos lineales pueden ser muy convenientes desde el punto de vista de la calidad. Y en otras \u00e1reas, la complejidad adicional del modelo y la capacidad proporcionada por las RNP puede ser m\u00e1s importante. Al definir la arquitectura del modelo, recuerda explorar tu problema lo suficiente como para saber en qu\u00e9 espacio te encuentras."
      ]
    },
    {
      "metadata": {
        "id": "9MquXy9zLS9B",
        "colab_type": "text"
      },
      "cell_type": "markdown",
      "source": [
        " ### *An\u00e1lisis opcional:* Ventajas y desventajas entre `embedding_column` y `indicator_column`\n",
        "\n",
        "A nivel conceptual, cuando entrenamos un `LinearClassifier` o un `DNNClassifier`, necesitamos una adaptaci\u00f3n para usar una columna dispersa. TF ofrece dos opciones: `embedding_column` o `indicator_column`.\n",
        "\n",
        "Al entrenar un clasificador lineal (como en la **Tarea\u00a01**), se usa una `embedding_column` como opci\u00f3n avanzada. Como se ve en la **Tarea\u00a02**, al entrenar un `DNNClassifier`, debes elegir expl\u00edcitamente `embedding_column` o `indicator_column`. En esta secci\u00f3n, se observa un ejemplo simple para analizar la diferencia entre ambas, as\u00ed como las ventajas y desventajas de usar una o la otra."
      ]
    },
    {
      "metadata": {
        "id": "M_3XuZ_LLS9C",
        "colab_type": "text"
      },
      "cell_type": "markdown",
      "source": [
        " Imagina que tenemos datos dispersos que contienen los valores `\"great\"`, `\"beautiful\"` y `\"excellent\"`. Dado que el tama\u00f1o del vocabulario que estamos usando aqu\u00ed es de $V = 50$, cada unidad (neurona) de la primera capa tendr\u00e1 50 ponderaciones. Denotamos el n\u00famero de t\u00e9rminos de una entrada dispersa mediante el uso de $s$. Por lo tanto, para los datos dispersos de este ejemplo, $s = 3$. Para una capa de entrada con $V$ valores posibles, una capa oculta con $d$ unidades debe hacer una multiplicaci\u00f3n de vector por matriz: $(1 \\times V) * (V \\times d)$. Esto tiene un costo de c\u00f3mputo de $O(V * d)$. Ten en cuenta que este costo es proporcional al n\u00famero de ponderaciones en una capa oculta e independiente de $s$.\n",
        "\n",
        "Si las entradas tienen codificaci\u00f3n de un solo 1 (un vector booleano con una longitud de $V$ con un 1 para los t\u00e9rminos presentes y un 0 para el resto) que usa una [`indicator_column`](https://www.tensorflow.org/api_docs/python/tf/feature_column/indicator_column), esto significa multiplicar y sumar muchos ceros."
      ]
    },
    {
      "metadata": {
        "id": "I7mR4Wa2LS9C",
        "colab_type": "text"
      },
      "cell_type": "markdown",
      "source": [
        " Cuando logramos exactamente los mismos resultados al usar una [`embedding_column`](https://www.tensorflow.org/api_docs/python/tf/feature_column/embedding_column) con un tama\u00f1o de $d$, buscamos y sumamos solamente aquellas incorporaciones correspondientes a las tres atributos presentes en la entrada de nuestro ejemplo de `\"great\"`, `\"beautiful\"` y `\"excellent\"`: $(1 \\times d) + (1 \\times d) + (1 \\times d)$. Dado que las ponderaciones de los atributos que est\u00e1n ausentes se multiplican por cero en la multiplicaci\u00f3n de vector por matriz, estas no contribuyen al resultado. Las ponderaciones de los atributos que est\u00e1n presentes se multiplican por 1 en la multiplicaci\u00f3n de vector por matriz. Por lo tanto, al sumar las ponderaciones obtenidas a trav\u00e9s de la b\u00fasqueda de incorporaciones, se obtendr\u00e1 el mismo resultado que en la multiplicaci\u00f3n de vector por matriz.\n",
        "\n",
        "Al usar una incorporaci\u00f3n, el c\u00f3mputo de la b\u00fasqueda de incorporaciones es un c\u00f3mputo de $O(s * d)$, el cual es mucho m\u00e1s eficiente con relaci\u00f3n al c\u00f3mputo que el costo de $O(V * d)$ para la `indicator_column` en datos dispersos, para los cuales $s$ es mucho m\u00e1s peque\u00f1o que $V$. (Recuerda que estas incorporaciones se est\u00e1n aprendiendo. En cualquier iteraci\u00f3n de entrenamiento dada, las ponderaciones actuales son las que se buscan.)"
      ]
    },
    {
      "metadata": {
        "id": "etZ9qf0kLS9D",
        "colab_type": "text"
      },
      "cell_type": "markdown",
      "source": [
        " Como vimos en la **Tarea\u00a03**, al usar una `embedding_column` al entrenar el `DNNClassifier`, nuestro modelo aprende una representaci\u00f3n de dimensiones bajas para los atributos, en la que el producto de puntos define una m\u00e9trica de similitud adaptada a la tarea deseada. En este ejemplo, los t\u00e9rminos que se usan de manera similar en el contexto de las rese\u00f1as de pel\u00edculas (p.\u00a0ej., `\"great\"` y `\"excellent\"`) estar\u00e1n m\u00e1s cerca entre s\u00ed en el espacio de incorporaci\u00f3n (es decir, tendr\u00e1n un producto de puntos de gran tama\u00f1o) y los t\u00e9rminos que son desemejantes (p.\u00a0ej., `\"great\"` y `\"bad\"`) estar\u00e1n m\u00e1s alejados entre s\u00ed en el espacio de incorporaci\u00f3n (es decir, tendr\u00e1n un producto de puntos peque\u00f1o)."
      ]
    }
  ]
}
